package com.mazaiting.gateway.security;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.mazaiting.auth.constant.AuthConstant;
import com.mazaiting.auth.constant.SecurityConst;
import com.mazaiting.gateway.domain.SecureIgnoreBean;
import com.mazaiting.redis.service.IRedisService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * 网关自定义鉴权管理器
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ResourceServerManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    private final IRedisService redisService;

    private final SecureIgnoreBean secureIgnoreBean;


    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        if (request.getMethod() == HttpMethod.OPTIONS) { // 预检请求放行
            return Mono.just(new AuthorizationDecision(true));
        }
        // 请求路径
        String path = request.getURI().getPath();
        log.info("进入 ResourceServerManager 校验, 请求路径: {}", path);
        // 判断是否在忽略 token 的列表中
        boolean ignoreMatch = secureIgnoreBean.getUrls().stream().anyMatch(s -> PathPatternParser.defaultInstance.parse(s).matches(PathContainer.parsePath(path)));
        if (ignoreMatch) { // 白名单
            return Mono.just(new AuthorizationDecision(true));
        }
        String method = request.getMethodValue();
        // RESTFul接口权限设计: https://www.cnblogs.com/haoxianrui/p/14961707.html
        String restfulPath = method + ":" + path.substring("/api".length());
//        // 如果token以"bearer "为前缀，到此方法里说明JWT有效即已认证
        String token = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        // 判断 token
        if (StringUtils.hasLength(token) && StringUtils.startsWithIgnoreCase(token, SecurityConst.JWT_PREFIX)) {
            if (path.contains("/test")) {
                // 白名单路径
                return Mono.just(new AuthorizationDecision(true));
            }
        } else {
            return Mono.just(new AuthorizationDecision(false));
        }
        // 判断是否需要权限
        List<String> roles = checkRequire(restfulPath);

        // 没有设置拦截规则放行
        if (CollectionUtil.isEmpty(roles)) {
            return Mono.just(new AuthorizationDecision(true));
        }

        // 判断JWT中携带的用户角色是否有权限访问
        return mono
                .filter(Authentication::isAuthenticated)
                .flatMapIterable(Authentication::getAuthorities)
                .map(GrantedAuthority::getAuthority)
                .any(authority -> {
                    String roleCode = StrUtil.removePrefix(authority,SecurityConst.AUTHORITY_PREFIX);// ROLE_ADMIN移除前缀ROLE_得到用户的角色编码ADMIN
                    if (SecurityConst.ROLE_ROOT_CODE.equals(roleCode)) {
                        return true; // 如果是超级管理员则放行
                    }
                    return CollectionUtil.isNotEmpty(roles) && roles.contains(roleCode);
                })
                .map(AuthorizationDecision::new)
                .defaultIfEmpty(new AuthorizationDecision(false));
    }

    /**
     * 鉴权开始
     * RESTFul接口权限设计: <a href="https://www.cnblogs.com/haoxianrui/p/14961707.html">...</a>
     *
     * @param restfulPath 接口路径
     *                    缓存取 [URL权限-角色集合] 规则数据
     *                    urlPermRolesRules = [{'key':'GET:/api/v1/users/*','value':['ADMIN','TEST']},...]
     */
    public List<String> checkRequire(String restfulPath) {
        PathMatcher pathMatcher = new AntPathMatcher();
        // 读取关系字符串
        Map<Object, Object> map = redisService.hGetAll(AuthConstant.URL_PERM_ROLES_KEY);
        for (Map.Entry<Object, Object> permRoles : map.entrySet()) {
            // 键值
            String perm = permRoles.getKey().toString();
            // 路径匹配
            if (pathMatcher.match(perm, restfulPath)) {
                return Convert.toList(String.class, permRoles.getValue());
            }
        }
        return Collections.emptyList();
    }
}
